#region "Copyright"

//PayPal Payflow Pro .NET SDK
//Copyright (C) 2014  PayPal, Inc.
//
//This file is part of the Payflow Pro .NET SDK
//
//The Payflow .NET SDK is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//any later version.
//
//The Payflow .NET SDK is is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with the Payflow .NET SDK.  If not, see <http://www.gnu.org/licenses/>.


#endregion

#region "Using"

using System;
using System.Collections;
//using System.Configuration;
//using System.Xml;
using System.IO;
//using System.Reflection;
using System.Security.Permissions;
using System.Security;
using PayPal.Payments.Common.Utility;
using PayPal.Payments.Communication;
#endregion

namespace PayPal.Payments.Common.Logging
{
	/// -----------------------------------------------------------------------------
	/// <summary>
	/// This is the main class which is used for Logging.It has a static constructor 
	/// which initializes the sortedlist containing all the error messages.
	/// This class will primarily be used by the Context class.
	/// The only method from this class which can be used outside the assembly is the log
	/// method which takes in a string and severity level as parameters.
	/// </summary>
	/// <remarks>
	///     
	/// </remarks>
	/// -----------------------------------------------------------------------------
	public sealed class Logger
	{
		#region "Member variable"
		/// <summary>
		/// This SortedList holds the Message code,Body and severity level of the messages
		/// mentioned in the XML file.
		/// </summary>
		private SortedList mMessages;
		/// <summary>
		/// Holds the instance of this singleton class.
		/// </summary>
		private static Logger mInstance;
		/// <summary>
		/// Holds the errors generated by Logger class.
		/// </summary>
		private ArrayList mLoggerErrs = new ArrayList();
		/// <summary>
		///	Holds log file name
		/// </summary>
		private String mLogFileName; 
		/// <summary>
		/// Holds Log file size
		/// </summary>
		private long mFileLength = 0;
		/// <summary>
		/// Holds log file 
		/// </summary>
		private FileInfo mFileInfo = null;
		/// <summary>
		/// This StermWriter writes in to Log file
		/// </summary>
		private StreamWriter mWriteToFile = null;
		/// <summary>
		/// Holds Log file size Limit from Config file
		/// </summary>
		private long mFileSize;  // = PayflowConstants.LOGFILE_SIZE;
		/// <summary>
		/// This flag indicates, if an error occurred because of the Logger class.
		/// </summary>
		private static bool mErrInLogger = false;
		/// <summary>
		/// Holds Log level info
		/// </summary>
		private int mLogLevel = 0;
		/// <summary>
		/// Holds Log file size limit 
		/// </summary>
		private string mFileSizeLimit;
		/// <summary>
		/// Hold COM information 
		/// </summary>
		private bool mIsCom= false;

		private String mStrLogLevel;
		public String mRequestId;
 
		#endregion
		
		#region "properties"

		#endregion

		#region "Private constructor"
		/// -----------------------------------------------------------------------------
		/// <summary>
		/// This private constructor reads the config file for the name of file which contains the 
		/// error messages.After getting the name it loads the static member "mMessages" with 
		/// the messages mentioned in the file.
		/// Gets Log file name from Config file. If not available then assign default file name.
		/// Gets Log file size from Config file. If not available then assign default file size.
		/// </summary>
		/// <returns>Nothing</returns>
		/// <remarks>
		/// </remarks>
		/// -----------------------------------------------------------------------------
		private Logger()
		{
			InitializeInstance();
		}

		private Logger(String LoggingLevel, String LogFileName, String MaxLogLimit, bool IsComWrapper)
		{
			mStrLogLevel = LoggingLevel;
			mLogFileName = LogFileName;
			mFileSizeLimit = MaxLogLimit;
			mIsCom = IsComWrapper;
			InitializeInstance();
		}
		
		private void InitializeInstance()
		{
			//FileSizeLimit="";
			string KeyName="";
			bool IsLogLevelKeyPresent = false;
			bool IsLogFilenameKeyPresent = false;
			bool IsLogFileSizeKeyPresent = false;
			
			/* Reads the config file to get the path of the file containing all the messages.
			 * It will then load all the message from this file into the hashtable.
			*/
			//Reads the XML message file and loads the messages into a error object.
			
			PopulateMessages();
			if(!mIsCom)
			{
				try
				{
					mStrLogLevel=PayflowUtility.AppSettings(PayflowConstants.CONFIG_LOG_LEVEL);
				}
				catch
				{
					KeyName=PayflowConstants.CONFIG_LOG_LEVEL;
					mLogLevel =  PayflowConstants.LOGGING_OFF;
					IsLogLevelKeyPresent = false;
				}
			}			

			if(mStrLogLevel == null || mStrLogLevel.Length == 0)
			{
				KeyName = PayflowConstants.CONFIG_LOG_LEVEL;
				mLogLevel =  PayflowConstants.LOGGING_OFF;
				IsLogLevelKeyPresent = false;
			}
			else
			{
				switch(((mStrLogLevel.Trim()).ToUpper()))
				{
					case "OFF":
						mLogLevel = PayflowConstants.LOGGING_OFF; 
						break;
					case "DEBUG":
						mLogLevel = PayflowConstants.SEVERITY_DEBUG; 
						break;
					case "INFO":
						mLogLevel= PayflowConstants.SEVERITY_INFO;
						break;
					case "WARN":
						mLogLevel= PayflowConstants.SEVERITY_WARN;
						break;
					case "ERROR":
						mLogLevel= PayflowConstants.SEVERITY_ERROR;
						break;
					case "FATAL":
						mLogLevel= PayflowConstants.SEVERITY_FATAL;
						break;
					default:
						mLogLevel = PayflowConstants.LOGGING_OFF; 
						break;
				}
			}
			 IsLogLevelKeyPresent =  (mLogLevel > PayflowConstants.LOGGING_OFF && mLogLevel 	<= PayflowConstants.SEVERITY_FATAL);



			// Do not need to do any further processing if Logging is set to OFF.
			if(mLogLevel != PayflowConstants.LOGGING_OFF)
			{
				if(!mIsCom)
				{
					try
					{
						mLogFileName = PayflowUtility.AppSettings(PayflowConstants.CONFIG_LOGFILE_NAME);
					}
					catch
					{
						if(String.Empty.Equals(KeyName))
						{
							KeyName=PayflowConstants.CONFIG_LOGFILE_NAME;
						}
						else
						{
							KeyName=KeyName + ", " + PayflowConstants.CONFIG_LOGFILE_NAME;
						}
						
					}
				}

				//				if (mLogFileName == null ||  mLogFileName.Length == 0)
				//				{
				//					mLogFileName = PayflowConstants.LOGFILE_NAME;
				//
				//				}

				// If no log file information found in .config file, create file name using relative path.
				if (mLogFileName == null ||  mLogFileName.Length == 0)
				{
					mLogFileName = PayflowConstants.LOGFILE_NAME;
					//					if(String.Empty.Equals(KeyName))
					//					{
					//						KeyName=PayflowConstants.CONFIG_LOGFILE_NAME;
					//					}
					//					else
					//					{
					//						KeyName=KeyName + ", " + PayflowConstants.CONFIG_LOGFILE_NAME;
					//					}
				}
				else
				{
					// mLogFileName = AppDomain.CurrentDomain.BaseDirectory + @"\" + mLogFileName;  Changed to Absolute (03/17/2007)
					IsLogFilenameKeyPresent =true;
				}
				



				if(!mIsCom)
				{
					try
					{
						mFileSizeLimit = PayflowUtility.AppSettings(PayflowConstants.CONFIG_LOGFILE_SIZE);
					}
					catch
					{
						//mFileSize = PayflowConstants.LOGFILE_SIZE; 
						if(String.Empty.Equals(KeyName))
						{
							KeyName=PayflowConstants.CONFIG_LOGFILE_SIZE;
						}
						else
						{
							KeyName = KeyName + ", " + PayflowConstants.CONFIG_LOGFILE_SIZE;
						}
					}
					
				}
				if (mFileSizeLimit == null  || mFileSizeLimit.Length == 0)
				{
					mFileSize = PayflowConstants.LOGFILE_SIZE; 
				}
				else
				{
					try
					{
						mFileSize = Int32.Parse(mFileSizeLimit);
					}
					catch
					{
						mFileSize = PayflowConstants.LOGFILE_SIZE; 
						if(String.Empty.Equals(KeyName))
						{
							KeyName=PayflowConstants.CONFIG_LOGFILE_SIZE;
						}
						else
						{
							KeyName = KeyName + ", " + PayflowConstants.CONFIG_LOGFILE_SIZE;
						}
					}
					if (mFileSize < 1)
						mFileSize = PayflowConstants.LOGFILE_SIZE; 
					IsLogFileSizeKeyPresent =true;
				}
			}
			
			if(!(IsLogLevelKeyPresent== true && IsLogFilenameKeyPresent== true && IsLogFileSizeKeyPresent== true))
			{
				if(!(IsLogLevelKeyPresent== false && IsLogFilenameKeyPresent== false && IsLogFileSizeKeyPresent== false))
				{
					ErrorObject Error;
					String RespMessage = PayflowConstants.PARAM_RESULT
						+ PayflowConstants.SEPARATOR_NVP
						+ (String)PayflowConstants.CommErrorCodes[PayflowConstants.E_CONFIG_ERROR]
						+ PayflowConstants.DELIMITER_NVP
						+ PayflowConstants.PARAM_RESPMSG
						+ PayflowConstants.SEPARATOR_NVP
						+ (String)PayflowConstants.CommErrorMessages[PayflowConstants.E_CONFIG_ERROR]
						+ "Tags "
						+ KeyName
						+ " are not present in the config file or config file is missing.";
					if (IsLogLevelKeyPresent== false)
					{
						Error = new ErrorObject(PayflowConstants.SEVERITY_ERROR, "",RespMessage);
					}
					else
					{
						Error = new ErrorObject(PayflowConstants.SEVERITY_WARN, "",RespMessage);
					}
					mLoggerErrs.Add(Error);
					//if(IsLogLevelKeyPresent )
				    //{
					//	InitializeStream();
					//}
				}
			}
			
		}

	#endregion

		#region "Get Instance"

			
		///<summary>
		///Holds the instance of this singleton class.
		///</summary>
		public static Logger Instance
		{
			get
			{
				lock (typeof (Logger))
				{
					if (mInstance == null)
						mInstance = new Logger();
					return mInstance;
				}
			}
		}
		/// <summary>
		/// Get the Errors generated from the Logger.
		/// </summary>
		public ArrayList GetLoggerErrs
		{
			get
			{
				return mLoggerErrs ;
			}
		}

		#endregion

		#region "Methods"
		/// -----------------------------------------------------------------------------
		/// <summary>
		/// This is a internal method and takes in a errorobject as a parameter.This method 
		/// then calls another overloaded version for the method Log which takes in a 
		/// string to be logged and the severity level of the Error Object.
		/// </summary>
		/// <param name="Message" >ErrorObject</param>
		/// <return></return>
		/// <remarks>
		/// </remarks>
		/// -----------------------------------------------------------------------------
		internal void Log(ErrorObject Message)
		{
			if (Message != null)
			{
				Log(Message.ToString(), Message.SeverityLevel);
			}
		}


		/// -----------------------------------------------------------------------------
		/// <summary>
		/// This is a internal method and takes in a arraylist of the errorobjects 
		/// as a parameter.The method goes through the array list for each errorobject 
		/// found makes a call to another overloaded version of the Log method which takes 
		/// in ErrorObject as a parameter.If there are some errors which have been generated by
		/// the Logger class then those are logged instead of the passed messages.
		/// </summary>
		/// <param name="Messages" >ArrayList containing the error objects</param>
		/// <return></return>
		/// <remarks>
		/// </remarks>
		/// -----------------------------------------------------------------------------
		internal void Log(ArrayList Messages) //ArrayList containing the error objects
		{
			int ErrCnt = 0;
			int ErrMaxCnt = 0;
			if (mLoggerErrs.Count != 0) // This means no error was previously generated in this class
			{
				Messages = mLoggerErrs;
			}
			ErrMaxCnt = Messages.Count;
			for (ErrCnt = 0; ErrCnt < ErrMaxCnt; ErrCnt++)
			{
				Log((ErrorObject) Messages[ErrCnt]);
			}
		}

		/// -----------------------------------------------------------------------------
		/// <summary>
		/// This method used to log the data in a file.Different type of severity level are logged here.
		/// The levels that can be logged are decided by the configuration settings in the 
		/// Application config file.
		/// </summary>
		/// <param name="Message">String value that needs to be logged</param>
		/// <param name="SeverityLvl">Severity level of the message.This could be one of the following:
		/// 	1 (Debug)
		/// 	2 (Info)
		/// 	3 (Warn)
		/// 	4 (Error)
		/// 	5 (Fatal)
		/// </param>
		/// <return></return>
		/// <remarks>
		/// </remarks>
		/// -----------------------------------------------------------------------------
		public void Log(String Message, int SeverityLvl)
		{
			String StrDebugLevel = String.Empty;
			String MessageToLog = String.Empty;
			if ( mLogLevel != 0 && ! mErrInLogger )
			{
				if (SeverityLvl >= mLogLevel)
				{
					try
					{
						switch (SeverityLvl)
						{
							case PayflowConstants.SEVERITY_DEBUG:
								StrDebugLevel = "DEBUG";
								break;
							case PayflowConstants.SEVERITY_INFO:
								StrDebugLevel = "INFO";
								break;
							case PayflowConstants.SEVERITY_WARN:
								StrDebugLevel = "WARN";
								break;
							case PayflowConstants.SEVERITY_ERROR:
								StrDebugLevel = "ERROR";
								break;
							case PayflowConstants.SEVERITY_FATAL:
								StrDebugLevel = "FATAL";
								break;
							default:
								return;
						}

						InitializeStream();
						//int nProcessID = System.Diagnostics.Process.GetCurrentProcess().Id;

						MessageToLog = "[" + DateTime.Now +":" + GlobalClass.GlobalVar.ToString() + ":[" + StrDebugLevel + "]-" + Message;
						mFileInfo.Refresh();
						mFileLength = mFileInfo.Length + MessageToLog.Length +1;
						mWriteToFile.WriteLine(MessageToLog);
						
						if(mFileLength >= mFileSize )
						{
							if (ArchivedLogFile())
								mFileLength= 0;
						}
					}
					catch(Exception Ex)
					{
						String StackTrace = PayflowConstants.EMPTY_STRING;
						PayflowUtility.InitStackTraceOn();
						if(PayflowConstants.TRACE_ON.Equals(PayflowConstants.TRACE))
						{
							StackTrace = ": " + Ex.StackTrace;
						}
						String RespMessage = PayflowConstants.PARAM_RESULT
							+ PayflowConstants.SEPARATOR_NVP
							+ (String)PayflowConstants.CommErrorCodes[PayflowConstants.E_LOG_ERROR]
							+ PayflowConstants.DELIMITER_NVP
							+ PayflowConstants.PARAM_RESPMSG
							+ PayflowConstants.SEPARATOR_NVP
							+ (String)PayflowConstants.CommErrorMessages[PayflowConstants.E_LOG_ERROR]
							+ PayflowConstants.MESSAGE_LOG_ERROR
							+ Ex.Message
							+ StackTrace;
						ErrorObject Err = new ErrorObject(
							PayflowConstants.SEVERITY_WARN, "",
							RespMessage);
						mLoggerErrs.Add(Err);
						mErrInLogger = true;
					}
				}
			}
	}
		/// -----------------------------------------------------------------------------
		/// <summary>
		/// This is a internal method.This method contains implementation for rolling file functionality.
		/// </summary>
		/// <return></return>
		/// <remarks>
		/// </remarks>
		/// -----------------------------------------------------------------------------
		private bool ArchivedLogFile()
		{
			bool ArchivedFile = false;
			FileInfo ArchivedLog = null;
			string TempFileName;
			mWriteToFile.Close();
			try
			{
				String LogfileName = mFileInfo.FullName;
				int	TargetPosition =LogfileName.LastIndexOf(mFileInfo.Extension);
				for(int i = 1; !ArchivedFile; i++)
				{
					TempFileName=LogfileName.Insert(TargetPosition,PayflowConstants.UNDERSCORE)
						.Insert(TargetPosition+1,i.ToString())
						.ToString();
                    ArchivedLog = new FileInfo(TempFileName);
                    if(!ArchivedLog.Exists)
					{
						mFileInfo.CopyTo(TempFileName);
						mFileInfo.Delete();
						ArchivedFile = true;
					}
				}
			}
			catch
			{
				ArchivedFile = false;
			}
			mWriteToFile = null;
			mFileInfo = null;
			return ArchivedFile;
		}

		/// -----------------------------------------------------------------------------
		/// <summary>
		/// This method populates each ErrorObject int the arraylist passed, with the 
		/// details from the SortedList held by the logger class.It populates the mLoggerErrs
		/// in case the relevant message code is not found.
		/// </summary>
		/// <param name="ErrObj">ErrorObject</param> 
		/// <returns>ErrorObject</returns>
		/// <remarks>
		/// </remarks>
		/// -----------------------------------------------------------------------------
		//Populate the error object with the details
		internal ArrayList PopulateErrorDetails(ArrayList ErrObj)
		{
			ErrorObject ErrMesg;
			ErrorObject RetErrorObj = null;
			ArrayList RetErrObjs = new ArrayList(0);
			int ErrCnt;
			int ErrMaxCnt;
			int SevLvlAssigned;
			String[] MesgParams;

			//Iterate through all the error objects in the array list 
			ErrMaxCnt = ErrObj.Count;
			for (ErrCnt = 0; ErrCnt < ErrMaxCnt; ErrCnt++)
			{
				if (((ErrorObject) ErrObj[ErrCnt]).MessageCode.Length != 0)
				{
					ErrMesg = (ErrorObject) mMessages[((ErrorObject) ErrObj[ErrCnt]).MessageCode];
					if (((ErrorObject) ErrObj[ErrCnt]).SeverityLevel != 0)
					{
						SevLvlAssigned = ((ErrorObject) ErrObj[ErrCnt]).SeverityLevel;
					}
					else
					{
						SevLvlAssigned = ErrMesg.SeverityLevel;
					}
					MesgParams = new String[((ErrorObject) ErrObj[ErrCnt]).MessageParams.Count];
					((ErrorObject) ErrObj[ErrCnt]).MessageParams.CopyTo(MesgParams);
					RetErrorObj = new ErrorObject(SevLvlAssigned,
						ErrMesg.MessageCode, ErrMesg.MessageBody,
						MesgParams,
						((ErrorObject) ErrObj[ErrCnt]).ErrorStackTrace);

				}
				else //It is an exception.
				{
					RetErrorObj = (ErrorObject) ErrObj[ErrCnt];
				}
				RetErrObjs.Add(RetErrorObj);
			}
			return RetErrObjs;
		}
		
		private void PopulateMessages()
		{
			mMessages = new SortedList();
			ErrorObject Err = null;
			Err = new ErrorObject(PayflowConstants.SEVERITY_INFO,PayflowConstants.MSG_COMMUNICATION_ERROR,"RESULT={0}&RESPMSG={1}");
			mMessages.Add(PayflowConstants.MSG_COMMUNICATION_ERROR, Err);
			Err = null;
			Err = new ErrorObject(PayflowConstants.SEVERITY_INFO,PayflowConstants.MSG_COMMUNICATION_ERROR_XMLPAY, "<XMLPayResponse xmlns='http://www.paypal.com/XMLPay'><ResponseData><TransactionResults><TransactionResult><Result>{0}</Result><Message>{1}</Message></TransactionResult></TransactionResults></ResponseData></XMLPayResponse>");
			mMessages.Add(PayflowConstants.MSG_COMMUNICATION_ERROR_XMLPAY, Err);
			Err = null;
			Err = new ErrorObject(PayflowConstants.SEVERITY_INFO,PayflowConstants.MSG_COMMUNICATION_ERROR_NO_RESPONSE_ID,"RESULT={0}&RESPMSG={1}");
			mMessages.Add(PayflowConstants.MSG_COMMUNICATION_ERROR_NO_RESPONSE_ID, Err);
			Err = null;
			Err = new ErrorObject(PayflowConstants.SEVERITY_INFO,PayflowConstants.MSG_COMMUNICATION_ERROR_XMLPAY_NO_RESPONSE_ID, "<XMLPayResponse xmlns='http://www.paypal.com/XMLPay'><ResponseData><TransactionResults><TransactionResult><Result>{0}</Result><Message>{1}</Message></TransactionResult></TransactionResults></ResponseData></XMLPayResponse>");
			mMessages.Add(PayflowConstants.MSG_COMMUNICATION_ERROR_XMLPAY_NO_RESPONSE_ID, Err);
			Err = null;

		}


		private void InitializeStream()
		{
			try
			{
				if ( (mFileInfo == null) || (mWriteToFile == null))
				{
							
					mFileInfo = new FileInfo(mLogFileName);
						
					if (mFileInfo.Extension.Length == 0)
					{
						mLogFileName = mLogFileName+".Log";
						mFileInfo = new FileInfo(mLogFileName);
					}
							
					if (!Directory.Exists(mFileInfo.DirectoryName))
					{
						Directory.CreateDirectory(mFileInfo.DirectoryName);
					}
					mWriteToFile = new StreamWriter(mLogFileName,true);
					mWriteToFile.AutoFlush = true;
				}
			}
			catch(Exception Ex)
			{
				String StackTrace = PayflowConstants.EMPTY_STRING;
				PayflowUtility.InitStackTraceOn();
				if(PayflowConstants.TRACE_ON.Equals(PayflowConstants.TRACE))
				{
					StackTrace = ": " + Ex.StackTrace;
				}
				String RespMessage = PayflowConstants.PARAM_RESULT
					+ PayflowConstants.SEPARATOR_NVP
					+ (String)PayflowConstants.CommErrorCodes[PayflowConstants.E_LOG_ERROR]
					+ PayflowConstants.DELIMITER_NVP
					+ PayflowConstants.PARAM_RESPMSG
					+ PayflowConstants.SEPARATOR_NVP
					+ (String)PayflowConstants.CommErrorMessages[PayflowConstants.E_LOG_ERROR]
					+ PayflowConstants.MESSAGE_LOG_ERROR
					+ " " +Ex.Message 
					+ " " +StackTrace;
				ErrorObject Err = new ErrorObject(
					PayflowConstants.SEVERITY_ERROR, "",
					RespMessage);
				mLoggerErrs.Add(Err);
				mErrInLogger = true;
			}

		}

		internal static void SetInstance(String LoggingLevel, String LogFileName, String MaxLogSize,bool IsCOMWrapper)
		{
			if(mInstance != null)
			{
				mInstance = null;
			}
			mInstance = new Logger(LoggingLevel,LogFileName,MaxLogSize,IsCOMWrapper);
		}
			#endregion
	} // END CLASS DEFINITION Logger

} // Payments.Common.Logging